use super::*;
use std::iter::zip;

impl MapGen {
    #[inline]
    fn land_close_to_water(&self, (x, y): (usize, usize)) -> bool {
        if self.base[x][y].height != 0 {
            return false;
        }

        let (m, n) = self.base_size();

        let sx = x as i32;
        let sy = y as i32;
        for (dx, dy) in zip(DX.iter(), DY.iter()) {
            let (nx, ny) = {
                let (nx, ny) = (sx + *dx, sy + *dy);
                if point_in_map((nx, ny), (m, n)) {
                    (nx as usize, ny as usize)
                } else {
                    continue;
                }
            };
            if self.base[nx][ny].is_land == LandOrWater::Water {
                return true;
            }
        }
        false
    }
    #[inline]
    pub fn gen_water(&mut self) -> GenResult<()> {
        if !self.config.enable_lake {
            return Ok(());
        }

        let mut available = Vec::new();

        let (m, n) = self.base_size();

        let count = self.config.lakes.get(m * n, &mut self.rng);
        let mut vis = self.new_vis();

        for _ in 0..count {
            available.clear();
            for (i, element) in self.base.iter().enumerate() {
                for (j, element) in element.iter().enumerate() {
                    if !vis[i][j] && element.is_land == LandOrWater::Land {
                        available.push((i, j));
                    }
                }
            }

            if available.len() == 0 {
                return Err(GenError::NoEnoughStartPoint(-1));
            }

            let selected = self.rng.gen_range(0..available.len());
            let area = self.config.lake_area.get(m * n, &mut self.rng);
            let bfs_option = BfsOption {
                startx: available[selected].0,
                starty: available[selected].1,
                is_rand: true,
                arg: area,
                vistied_map: Some(&mut vis),
                init: Box::new(|gen, (x, y), _| {
                    gen.base[x][y].is_land = LandOrWater::Water;
                    gen.base[x][y].height = -1;
                    1
                }),
                filter: Box::new(|gen, (x, y), _| gen.base[x][y].is_land == LandOrWater::Land),
                block_action: Box::new(|gen, (x, y), count, arg| {
                    gen.base[x][y].is_land = LandOrWater::Water;
                    gen.base[x][y].height = -1;
                    *count += 1;
                    if *count >= *arg {
                        return false;
                    }
                    true
                }),
                end_action: Box::new(|_, count, arg| {
                    if *count < *arg {
                        return Err(GenError::NoEnoughStartPoint(-1));
                    }
                    Ok(())
                }),
            };
            self.std_bfs(bfs_option)?;
        }
        for i in 0..m {
            for j in 0..n {
                if !vis[i][j] && self.base[i][j].is_land == LandOrWater::Land {
                    if !self.base[i][j].close_to_low && self.land_close_to_water((i, j)) {
                        self.base[i][j].close_to_low = true;
                    }
                }
            }
        }

        Ok(())
    }
    #[inline]
    fn close_to_low(&self, (x, y): (usize, usize)) -> bool {
        let cur_height = self.base[x][y].height;
        let (m, n) = self.base_size();

        let sx = x as i32;
        let sy = y as i32;
        for (dx, dy) in zip(DX.iter(), DY.iter()) {
            let (nx, ny) = {
                let (nx, ny) = (sx + *dx, sy + *dy);
                if point_in_map((nx, ny), (m, n)) {
                    (nx as usize, ny as usize)
                } else {
                    continue;
                }
            };
            if self.base[nx][ny].is_land == LandOrWater::Land
                && self.base[nx][ny].height == cur_height - 1
            {
                return true;
            }
        }
        false
    }
    #[inline]
    fn get_availabe(&self, cur_height: i8) -> Vec<(usize, usize)> {
        let mut available = Vec::new();
        for (i, element) in self.base.iter().enumerate() {
            for (j, element) in element.iter().enumerate() {
                if element.is_land == LandOrWater::Land
                    && element.height == cur_height - 1
                    && !element.close_to_low
                {
                    available.push((i, j));
                }
            }
        }
        available
    }
    #[inline]
    pub fn gen_height(&mut self) -> GenResult<()> {
        let mut available;

        let mut err = None;

        for cur_height in 1..=self.config.top_height {
            available = self.get_availabe(cur_height);
            let count = self.config.high_ground.get(available.len(), &mut self.rng);
            for _ in 0..count {
                available = self.get_availabe(cur_height);

                if available.len() == 0 {
                    return Err(GenError::NoEnoughStartPoint(cur_height));
                }

                let selected = self.rng.gen_range(0..available.len());
                let area = self
                    .config
                    .high_ground_area
                    .get(available.len(), &mut self.rng);
                let bfs_option = BfsOption {
                    startx: available[selected].0,
                    starty: available[selected].1,
                    arg: (cur_height, area),
                    is_rand: true,
                    vistied_map: None,
                    init: Box::new(|gen, (x, y), arg| {
                        gen.base[x][y].is_land = LandOrWater::Land;
                        gen.base[x][y].height = arg.0;

                        let mut edited = Vec::new();
                        edited.push((x, y));
                        (1, edited)
                    }),
                    filter: Box::new(|gen, (x, y), arg| {
                        gen.base[x][y].is_land == LandOrWater::Land
                            && gen.base[x][y].height == arg.0 - 1
                            && !gen.base[x][y].close_to_low
                    }),
                    block_action: Box::new(|gen, (x, y), count, arg| {
                        count.0 += 1;
                        gen.base[x][y].is_land = LandOrWater::Land;
                        gen.base[x][y].height = arg.0;
                        count.1.push((x, y));
                        if count.0 >= arg.1 {
                            return false;
                        }
                        true
                    }),
                    end_action: Box::new(|gen, count, arg| {
                        for (x, y) in count.1.iter() {
                            if gen.close_to_low((*x, *y)) {
                                gen.base[*x][*y].close_to_low = true;
                            }
                        }

                        if count.0 < arg.1 {
                            return Err(GenError::NoEnoughArea(arg.0));
                        }
                        Ok(())
                    }),
                };
                self.std_bfs(bfs_option).unwrap_or_else(|rerr| {
                    err = Some(rerr);
                    (0, Vec::with_capacity(0))
                });
                // self.display_base();
                // println!("\n\n\n");
            }
        }

        if let Some(err) = err {
            return Err(err);
        }

        Ok(())
    }
}
